package org.splink.library.queue {
	import org.splink.library.error.UnsupportedOperationError;
	import org.splink.library.utils.ArrayUtils;

	/**
	 * <code>ParallelQ</code> in contrast to <code>Q</code> starts all previously added
	 * <code>IQable</code> implementations in parallel. When all items are complete,
	 * <code>ParallelQ</code> distributes a <code>QEvent.COMPLETE</code> event.
	 * While the <code>ParallelQ</code> progresses, a <code>QEvent.PROGRESS</code> is distributed
	 * which contains information on the queue's overall progress. Because of the <code>ParallelQ</code>'s
	 * nature the progress information always describes the overall queue progress. 
	 * 
	 * @author Max Kugland
	 */
	public class ParallelQ extends Qable implements IQ {
		private var _items : Array = new Array();		private var _completed : Array = new Array();
		private var _running : Boolean = false;
		private var _progress : Object;

		public function ParallelQ(id : String = "ParallelQ") {
			super(id);
		}

		/**
		 * @inheritDoc
		 */
		override protected function doStart() : void {
			_progress = new Object();
			_running = true;
			for each (var item : IQable in _items) {
				if(_completed.indexOf(item) == -1) {
					registerItem(item);
					item.start();
				}
			}

			if(_items.length == _completed.length) {
				_running = false;
				complete();
			}
		}

		private function onItemProgress(e : QEvent) : void {
			_progress[e.id] = e.progress;
			
			var min : int = 100;
			var choice : Progress;
			var count : int = 0;
			for (var pid : String in _progress) {
				var prg : Progress = _progress[pid];
				if(prg.percent <= min) {
					min = prg.percent;
					choice = prg;
				}
				count++;
			}

			var evt : QEvent = new QEvent(this, QEvent.PROGRESS, id);
			evt.queueProgress.total = Q.getTotal(this, 0);
			evt.queueProgress.current = Math.round(choice.percent * evt.queueProgress.total / 100);
			evt.progress = choice.clone();

			distribute(evt);
		}

		private function onItemError(e : QEvent) : void {
			error(e.errorMessage);
			if(!(e.source is IQ)) {
				doOnItemComplete(IQable(e.source));
			}
		}

		private function onItemComplete(e : QEvent) : void {
			doOnItemComplete(IQable(e.source));
		}

		private function doOnItemComplete(qable : IQable) : void {
			_completed.push(unregisterItem(qable));
			if(_items.length == _completed.length) {
				complete();
			}
		}

		private function registerItem(item : IQable) : void {
			item.register(QEvent.COMPLETE, onItemComplete);
			item.register(QEvent.ERROR, onItemError);
			item.register(QEvent.PROGRESS, onItemProgress);
		}

		private function unregisterItem(item : IQable) : IQable {
			item.unregister(QEvent.COMPLETE, onItemComplete);
			item.unregister(QEvent.ERROR, onItemError);
			item.unregister(QEvent.PROGRESS, onItemProgress);
			return item;
		}

		/**
		 * @inheritDoc
		 */
		override protected function doStop() : void {
			_running = false;
			for each (var item : IQable in _items) {
				unregisterItem(item).stop();
			}
		}

		/**
		 * @inheritDoc
		 */
		override protected function doReset() : void {
			_running = false;
			for each (var item : IQable in _items) {
				unregisterItem(item);
				item.reset();
			}
			_completed = new Array();
		}

		/**
		 * @inheritDoc
		 */
		override protected function doFinalize() : void {
			_running = false;
			for each (var item : IQable in _items) {
				item.finalize();
			}
			_items = new Array();
		}

		/**
		 * @inheritDoc
		 */
		public function add(item : IQable) : IQable {
			_items.push(item);
			return item;
		}

		/**
		 * @inheritDoc
		 */
		public function remove(item : IQable) : IQable {
			return ArrayUtils.remove(_items, item);
		}

		/**
		 * @inheritDoc
		 */
		public function getForId(id : String) : IQable {
			for each (var item : IQable in _items) {
				if(item.id == id) {
					return item;
				} 
			}
			
			return null;
		}

		/**
		 * @inheritDoc
		 */
		public function getAt(index : uint) : IQable {
			return _items[index];
		}

		/**
		 * Throws an <code>UnsupportedOperationError</code> as there can't be a current item in a <code>ParallelQ</code>
		 * 
		 * @inheritDoc
		 */
		public function get current() : IQable {
			throw new UnsupportedOperationError();
		}

		/**
		 * @inheritDoc
		 */
		public function get length() : int {
			return _items.length;
		}

		/**
		 * @inheritDoc
		 */
		public function get isRunning() : Boolean {
			return _running;
		}
	}
}
